package query

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/glennliao/apijson-go/config"
	"github.com/glennliao/apijson-go/model"
	"github.com/glennliao/apijson-go/util"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/samber/lo"
)

type C查询结构体 struct {
	S上下文 context.Context

	// json请求内容
	Q请求数据 model.Map
	// 节点树根节点
	G根节点 *J节点

	// 用于根据path获取节点
	L路径节点映射 map[string]*J节点

	K开始时间 time.Time
	J结束时间 time.Time

	C错误 error

	// 输出过程
	D打印过程日志 bool

	// 关闭权限验证,默认否
	B不验证访问 bool

	C查询配置 *config.C查询配置

	// 自定义可访问权限的限定,例如添加用户id的where条件
	F访问条件 config.M访问条件函数

	S数据库元数据 *config.S数据库元信息

	// 数据库字段命名风格 请求传递到数据库中
	S数据库字段风格 config.Z字段风格转换函数

	// json字段风格 数据库返回的字段
	Json字段风格 config.Z字段风格转换函数
}

func X新建查询(上下文 context.Context, 查询配置 *config.C查询配置, 请求数据 model.Map) *C查询结构体 {

	查询 := &C查询结构体{
		C查询配置: 查询配置,
	}

	查询.C初始化(上下文, 请求数据)
	查询.B不验证访问 = 查询配置.W无需验证()

	return 查询
}

func (查询 *C查询结构体) C初始化(上下文 context.Context, 请求数据 model.Map) {

	查询.S上下文 = 上下文
	查询.Q请求数据 = 请求数据

	查询.K开始时间 = time.Now()
	查询.L路径节点映射 = make(map[string]*J节点)
}

func (查询 *C查询结构体) H获取结果() (model.Map, error) {

	if 查询.D打印过程日志 {
		g.Log().Debugf(查询.S上下文, "【query】 ============ [begin]")
		g.Log().Debugf(查询.S上下文, "【query】 ============ [buildNodeTree]")
	}

	// 构建节点树,并校验结构是否符合,不符合则返回错误,结束本次查询
	查询.G根节点 = X新建节点(查询, "", "", 查询.Q请求数据)

	错误 := 查询.G根节点.G构建子节点()

	if 错误 != nil {
		return nil, 错误
	}

	if 查询.D打印过程日志 {
		查询.D打印节点树(查询.G根节点, 0)
	}

	if 查询.D打印过程日志 {
		g.Log().Debugf(查询.S上下文, "【query】 ============ [parse]")
	}

	S设置节点角色(查询.G根节点, "", "")

	查询.G根节点.J解析()

	if 查询.D打印过程日志 {
		g.Log().Debugf(查询.S上下文, "【query】 ============ [fetch]")
	}

	查询.H获取数据()

	if 查询.C错误 != nil {
		return nil, 查询.C错误
	}

	结果映射, 错误 := 查询.G根节点.J结果()

	if 错误 != nil {
		if 查询.G根节点.C错误 != nil {
			return nil, 查询.G根节点.C错误
		}

		返回结果 := model.Map{}
		for 键, 节点 := range 查询.G根节点.Z子节点 {
			返回结果[键] = 节点.C错误
		}
		return 返回结果, 错误
	}

	if 查询.D打印过程日志 {
		g.Log().Debugf(查询.S上下文, "【query】 ^=======================^")
	}

	return 结果映射.(model.Map), 错误
}

func (查询 *C查询结构体) H获取数据() {
	// 分析依赖关系

	var 依赖关系 [][]string
	F分析引用(查询.G根节点, &依赖关系)
	获取队列, 错误 := util.F分析顺序(依赖关系)

	if 错误 != nil {
		查询.C错误 = 错误
		return
	}

	for 路径, _ := range 查询.L路径节点映射 {
		if !lo.Contains(获取队列, 路径) {
			获取队列 = append(获取队列, 路径)
		}
	}

	if 查询.D打印过程日志 {
		g.Log().Debugf(查询.S上下文, "fetch queue： %s", strings.Join(获取队列, " > "))
	}

	for _, 路径 := range 获取队列 {
		查询.L路径节点映射[路径].H获取数据()
	}

	查询.G根节点.H获取数据()
}

// 输出节点信息
func (查询 *C查询结构体) D打印节点树(节点 *J节点, 深度 int) {

	for i := 0; i < 深度; i++ {
		fmt.Print("|")
	}

	描述 := gconv.String(节点.L类型)
	if 节点.S是列表 {
		描述 += "[]"
	}

	格式 := fmt.Sprintf("- %%-%ds | %%s\n", 20-深度)

	fmt.Printf(格式, 节点.J键, 描述)

	for _, 子节点 := range 节点.Z子节点 {
		查询.D打印节点树(子节点, 深度+1)
	}
}
